/**
 * kb-matrix.c - 矩阵键盘
 * by Nixsawe <ziming_cool@126.com>
 * 2021-12-14 ~ 2022-1-16
 * 
 * 为了更方便地进行矩阵键盘按键的扫描，特封装此库
 * 所有的按键编码从 0 开始
 */

#include "kb-matrix.h"

unsigned char kb_keys[16];	// 存储线反转法扫描以及遍历式扫描的扫描结果
unsigned char last_keys[16];

// 判断键盘矩阵是否有按键被按下
unsigned char kbHasKeyDown()
{
	// 置列为高电平，行为低电平
	KBMTX_KEYS = 0xf;

	// 若有任何按键被按下，则对应的列会被连通的行拉低，则读出的列电平肯定有变化
	// 否则，则无按键按下
	return (KBMTX_KEYS != 0xf);
}

// 判断特定的键是否按下
unsigned char kbIsKeyPressed(unsigned char k)
{
	// 仅置对应的行为低电平
	unsigned char dat = _cror_(0x7f, k >> 2);

	// 检查列
	KBMTX_KEYS = dat;

	return !(KBMTX_KEYS & (8u >> (k & 3)));
}

// 逐个判断单个按键的状态（调用 kbIsKeyPressed），然后汇总，扫描结果保存到 kb_keys 中
// 仅当两次扫描结果相同时才更新按键状态，用来消除抖动
// 单次调用基准 1293 周期
void kbRawTraversalScan()
{
	unsigned char k;
	for(k = 0; k < 16; k ++)
	{
		if(last_keys[k] == kbIsKeyPressed(k))
		{
			kb_keys[k] = last_keys[k];
		}
		else
		{
			last_keys[k] = !last_keys[k];
		}
	}
}

// 逐个判断单个按键的状态（调用 kbIsKeyPressed），然后汇总，扫描结果保存到 kb_keys 中
// 没有处理消抖，仅一次调用即可获得扫描结果
// 单次调用基准 1149 周期
void kbFastRawTraversalScan()
{
	unsigned char k;
	for(k = 0; k < 16; k ++)
	{
		kb_keys[k] = kbIsKeyPressed(k);
	}
}

// 逐行逐列遍历式扫描，扫描结果保存到 kb_keys 中
// 仅当两次扫描结果相同时才更新按键状态，用来消除抖动
// 冲突仍然存在，但效果较普通的线反转式扫描更好些，一个矩形的三个端点均被按下时，该矩形剩下的那个端点也认为被按下
// 单次调用基准 758 周期
void kbTraversalScan()
{
	unsigned char i, j, dat, t;
	unsigned char row[4];
	// 仅置对应的行为低电平
	dat = 0x7f;

	for(i = 0; i < 16; i += 4)
	{
		KBMTX_KEYS = dat;
		dat = (dat >> 1) | 0x80;

		// 读列线
		t = KBMTX_KEYS;
		row[0] = !(t & 0x8);
		row[1] = !(t & 0x4);
		row[2] = !(t & 0x2);
		row[3] = !(t & 0x1);

		for(j = 0; j < 4; j ++)
		{
			if(last_keys[i + j] == row[j])
			{
				kb_keys[i + j] = last_keys[i + j];
			}
			else
			{
				last_keys[i + j] = !last_keys[i + j];
			}
		}
	}
}

// 逐行逐列遍历式扫描，扫描结果保存到 kb_keys 中
// 没有处理消抖，仅一次调用即可获得扫描结果
// 冲突仍然存在，但效果较普通的线反转式扫描更好些，一个矩形的三个端点均被按下时，该矩形剩下的那个端点也认为被按下
// 单次调用基准 242 周期
void kbFastTraversalScan()
{
	unsigned char i, dat, t;
	// 仅置对应的行为低电平
	dat = 0x7f;

	for(i = 0; i < 16; i += 4)
	{
		KBMTX_KEYS = dat;
		dat = (dat >> 1) | 0x80;

		// 读列线
		t = KBMTX_KEYS;

		kb_keys[i] = !(t & 0x8);
		kb_keys[i + 1] = !(t & 0x4);
		kb_keys[i + 2] = !(t & 0x2);
		kb_keys[i + 3] = !(t & 0x1);
	}
}

// 线反转扫描，扫描结果保存到 kb_keys 中
// 保留了最后一次的扫描结果，仅当两次扫描结果相同时才更新按键状态，用来消除抖动
// 故实际使用时，调用间应有一定延时，初次运行时至少两次调用才能得到扫描结果
// 多按键同时按下时仍可以工作，但由按键所连对角线所确定的矩形四角均会认为被按下
// 单次调用基准 841 周期
void kbCrossScan()
{
	unsigned char i, j, t;
	unsigned char line[4], row[4];

	// 置列为高电平
	KBMTX_KEYS = 0xf;

	// 读取列电平值
	t = KBMTX_KEYS;

	row[0] = t & 0x8;
	row[1] = t & 0x4;
	row[2] = t & 0x2;
	row[3] = t & 0x1;

	// 置行为高电平
	KBMTX_KEYS = 0xf0;

	// 读取行电平值
	t = KBMTX_KEYS;

	line[0] = t & 0x80;
	line[1] = t & 0x40;
	line[2] = t & 0x20;
	line[3] = t & 0x10;

	for(i = 0; i < 4; i ++)	// 行
	{
		for(j = 0; j < 4; j ++)	// 列
		{
			t = (i << 2) + j;
			if(last_keys[t] == (!line[i] && !row[j]))
			{
				kb_keys[t] = last_keys[t];
			}
			else
			{
				last_keys[t] = !last_keys[t];
			}
		}
	}
}

// 线反转扫描，扫描结果直接保存到 kb_keys 中
// 没有处理消抖，仅一次调用即可获得扫描结果
// 多按键同时按下时仍可以工作，但由按键所连对角线所确定的矩形四角均会认为被按下
// 单次调用基准 521 周期
void kbFastCrossScan()
{
	unsigned char i, j;
	unsigned char dat;
	unsigned char line[4], row[4];

	// 置列为高电平
	KBMTX_KEYS = 0xf;

	// 读取列电平值
	dat = KBMTX_KEYS;

	row[0] = dat & 0x8;
	row[1] = dat & 0x4;
	row[2] = dat & 0x2;
	row[3] = dat & 0x1;

	// 置行为高电平
	KBMTX_KEYS = 0xf0;

	// 读取行电平值
	dat = KBMTX_KEYS;

	line[0] = dat & 0x80;
	line[1] = dat & 0x40;
	line[2] = dat & 0x20;
	line[3] = dat & 0x10;

	for(i = 0; i < 4; i ++)	// 行
	{
		for(j = 0; j < 4; j ++)	// 列
		{
			kb_keys[(i << 2) + j] = (!line[i] && !row[j]);
		}
	}
}

// 线反转法扫描，返回按键值，仅当单独一个按键被按下时能准确识别
// 同时按下多个键时将返回序号最小的直角端点，没有按键被按下时，函数返回 -1
// 无消抖机制
// 单次调用基准 38 周期
int kbCrossRead()
{
	int key_pressing = -1;
	unsigned char t;

	KBMTX_KEYS = 0x0f;
	t = KBMTX_KEYS;

	if(!(t & 0x08))
	{
		key_pressing = 0;
	}
	else if(!(t & 0x04))
	{
		key_pressing = 1;
	}
	else if(!(t & 0x02))
	{
		key_pressing = 2;
	}
	else if(!(t & 0x01))
	{
		key_pressing = 3;
	}

	KBMTX_KEYS = 0xf0;
	t = KBMTX_KEYS;

	if(!(t & 0x80))
	{
	}
	else if(!(t & 0x40))
	{
		key_pressing += 4;
	}
	else if(!(t & 0x20))
	{
		key_pressing += 8;
	}
	else if(!(t & 0x10))
	{
		key_pressing += 12;
	}

	return key_pressing;
}

